<script setup lang="ts">
import { onMounted, ref, getCurrentInstance, type PropType } from 'vue'
import VUploaderFileItem from './VUploaderFileItem.vue'
import { NButton, useMessage } from 'naive-ui'
import Uploader from 'simple-uploader.js'
import type { CustomFile } from './types'
import { sendRequest } from '@/utils/api'
// @ts-ignore
import SparkMD5 from 'spark-md5'

const isDefine = <T = unknown>(val: T): val is NonNullable<T> => val !== undefined && val !== null

const props = defineProps({
  defaultFileList: {
    type: Array as PropType<CustomFile[]>
  },
  fileList: {
    type: Array as PropType<CustomFile[]>,
    default: () => []
  },
  accept: {
    type: String
  },
  isDirectory: {
    type: Boolean,
    default: false
  },
  singleFile: {
    type: Boolean,
    default: false
  },
  max: {
    type: Number
  }
})

const emit = defineEmits<{
  (event: 'update:file-list', fileList: CustomFile[]): void
}>()

const message = useMessage()

const options = {
  target: import.meta.env.VITE_API_BASE_URL + '/common/bigFile/uploader/chunk',
  chunkSize: '2048000',
  fileParameterName: 'upfile',
  maxChunkRetries: 3,
  testChunks: true,
  checkChunkUploadedByResponse(chunk: any, response_msg: string) {
    const objMessage = JSON.parse(response_msg)
    if (objMessage.skipUpload) {
      return true
    }
    return (objMessage.uploadedChunks || []).indexOf(chunk.offset + 1) >= 0
  }
}

const uploaderTriggerRef = ref(null)
const uploader = new Uploader(options)
const instance = getCurrentInstance()

onMounted(configUploader)
onMounted(listenerUploader)
onMounted(initDefaultFileList)

function initDefaultFileList() {
  if (!props.defaultFileList?.length) return
  props.defaultFileList.forEach((file: CustomFile) => {
    file.ignored = true
    uploader.addFile(file)
  })
  emit('update:file-list', uploader.fileList)
}

function configUploader() {
  if (uploaderTriggerRef.value) {
    uploader.assignBrowse(uploaderTriggerRef.value, props.isDirectory, props.singleFile, {
      accept: props.accept
    })
  }
}

function checkFileAddPass(file?: CustomFile) {
  if (isDefine(props.max) && uploader.fileList.length > props.max) {
    message.warning(`最多上传${props.max}个文件`)
    return false
  }

  if (file) {
    const dupNameFiles = uploader.fileList.filter((f: CustomFile) => f.name === file.name)
    if (dupNameFiles.length > 1) {
      message.warning(`文件${file.name}已存在`)
      return false
    }
  }
}

function listenerUploader() {
  uploader.on('fileAdded', (file: CustomFile) => {
    const f = checkFileAddPass(file)
    if (isDefine(f)) return f
    if (!file.file) return false
    if (!file.file.ignored) computeMD5(file)
  })

  uploader.on('filesAdded', () => {
    const f = checkFileAddPass()
    if (isDefine(f)) return f
  })

  uploader.on('fileRemoved', () => {
    emit('update:file-list', uploader.fileList)
    if (instance) instance.proxy?.$forceUpdate()
  })

  uploader.on('filesSubmitted', () => {
    emit('update:file-list', uploader.fileList)
    // uploader.upload()
    if (instance) instance.proxy?.$forceUpdate()
  })

  uploader.on('fileError', (rootFile: CustomFile, file: CustomFile, message: string) => {
    console.log('[某个文件上传失败了]', rootFile, file, message)
  })

  uploader.on('fileSuccess', async (_: CustomFile, file: CustomFile) => {
    try {
      file.refProjectId = '123456789'
      const [err, res] = await sendRequest({
        module: 'upload',
        api: 'mergeFile',
        override: {
          data: file
        }
      })
      if (err) throw err
      if (res.code !== 0) throw new Error(res.msg)
      file.file.remoteId = res.data.fileId
      message.success(res.msg)

      if (instance) instance.proxy?.$forceUpdate()
    } catch (error) {
      if (error instanceof Error) message.error(error.message)
    }
  })
}

//单个文件的大小限制2G
const fileSizeLimit = 200 * 1024 * 1024 * 1024
const chunkSize = 10 * 1024 * 1000
//由于计算整个文件的Md5太慢，因此采用只计算第1块文件的md5的方式
const chunkNumberMD5 = 1

function computeMD5(file: CustomFile) {
  file.pause()

  if (file.size > fileSizeLimit) {
    message.warning('文件大小不能超过2G')
    file.cancel()
    return
  }

  let currentChunk = 0
  const fileReader = new FileReader()
  const blobSlice = File.prototype.slice
  const spark = new SparkMD5.ArrayBuffer()

  loadNext()

  fileReader.onload = (e) => {
    if (!e.target) return
    spark.append(e.target.result)

    if (currentChunk < chunkNumberMD5) {
      loadNext()
    } else {
      let md5 = spark.end()
      file.uniqueIdentifier = md5
      file.resume()
      // console.log(
      //   `MD5计算完毕：${file.name} \nMD5：${md5} \n分片：${chunks} 大小:${file.size} 用时：${
      //     new Date().getTime() - time
      //   } ms`
      // )
    }
  }

  fileReader.onerror = () => {
    message.error(`文件${file.name}读取出错，请检查该文件`)
    file.cancel()
  }

  function loadNext() {
    let start = currentChunk * chunkSize
    let end = start + chunkSize >= file.size ? file.size : start + chunkSize

    fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end))
    currentChunk++
    // console.log('计算第' + currentChunk + '块')
  }
}
</script>

<template>
  <div class="uploader">
    <span
      ref="uploaderTriggerRef"
      class="uploader-trigger"
    >
      <NButton
        type="primary"
        ghost
      >
        点击上传
      </NButton>
    </span>

    <div class="uploader-files">
      <ul>
        <VUploaderFileItem
          v-for="(file, i) in fileList"
          :key="i"
          :file="file"
        />
      </ul>
    </div>
  </div>
</template>

<style scoped>
.uploader {
  display: flex;
  flex-direction: column;
}

.uploader-trigger {
  width: min-content;
  margin-bottom: 15px;
}

.uploader-files ul {
  display: flex;
  flex-direction: column;
  gap: 10px 0;
  list-style: none;
}
</style>
